#include <framework/framework_i.h>

#define debug(x ...) // kprintf(x)
#define error(x ...) kprintf(x)

/* Some constants */
static const char * EH_TYPE[5] =
{
	 "NONE",
	 "Relocatable",
	 "Executable",
	 "Shared Object",
	 "Core",
};

static const char * EH_MACHINE[8] =
{
	 "NONE",
	 "AT&T WE 32100",
	 "SPARC",
	 "Intel 80386",
	 "Motorola 68000",
	 "Motorola 88000",
	 "Intel 80860",
	 "MIPS RS3000",
};

static const char * SH_TYPE[12] =
{
	 "NULL",
	 "PROGBITS",
	 "SYMTAB",
	 "STRTAB",
	 "RELA",
	 "HASH",
	 "DYNAMIC",
	 "NOTE",
	 "NOBITS",
	 "REL",
	 "SHLIB",
	 "DYNSYM",
};

#define SH_TYPE_SYMTAB 2
#define SH_TYPE_STRTAB 3
#define SH_TYPE_NOBITS 8
#define SH_TYPE_RELOC  9

static const char * SYMTAB_BIND[3] =
{
	 "LOCAL",
	 "GLOBAL",
	 "WEAK",
};

static const char * SYMTAB_TYPE[5] =
{
	 "NO TYPE",
	 "OBJECT",
	 "FUNCTION",
	 "SECTION",
	 "FILE",  
};

#define GET_RELOC_SYM(i)  ((i)>>8)
#define GET_RELOC_TYPE(i) ((unsigned char)(i))

#define GET_SYMTAB_BIND(i)   ((i)>>4)
#define GET_SYMTAB_TYPE(i)   ((i)&0xf)

#define SHN_UNDEF  0
#define SHN_COMMON 0xfff2

#define MOD_LOAD_SYMBOL   "module_load"
#define MOD_UNLOAD_SYMBOL "module_unload"

static const char *
get_symbol_string(void *elf, uint32_t index)
{  
	 uint32_t i;
	 const char * ret;
	 struct elf_s *eh;
	 struct secthdr_s *sh;
	 struct symtab_s *symtab;

	 eh = (struct elf_s*)elf;

	 for (i = 0; i < eh->e_shnum; i ++)
	 {  
		  sh = (struct secthdr_s*)((uintptr_t)elf + eh->e_shoff + (i * eh->e_shentsize));
		  if(sh->sh_type == SH_TYPE_SYMTAB)
		  {
			   symtab = (struct symtab_s*)((uintptr_t)elf + sh->sh_offset);
			   sh = (struct secthdr_s*)(elf + eh->e_shoff + (sh->sh_link * eh->e_shentsize));
        
			   if(sh->sh_type == SH_TYPE_STRTAB)
			   {
					if (index == 0)
					{
						 return " ";
					}
					else return (char *)((uintptr_t)elf + sh->sh_offset + index);
			   }
		  }
	 }

	 ret = "";
	 return ret;
}

#define CHARS_MAX            10240
static uint32_t char_count;
static char     chars[CHARS_MAX];

#define EXPORT_SYM_COUNT_MAX 1024
#define EXPORT_SYM_HASH      197

static uint32_t    ex_sym_count;

static int         ex_sym_f[EXPORT_SYM_HASH];
static const char *ex_sym_name[EXPORT_SYM_COUNT_MAX];
static uintptr_t   ex_sym_ptr[EXPORT_SYM_COUNT_MAX];
static uint32_t    ex_sym_flags[EXPORT_SYM_COUNT_MAX];
static int         ex_sym_n[EXPORT_SYM_COUNT_MAX];

static uint32_t
sym_hash(const char *name, uint32_t len)
{
	 int idx;
	 uint32_t result = 0;
	 for (idx = 0; idx != len; ++ idx)
		  result = (result * 13 + name[idx]) % EXPORT_SYM_HASH;
	 return result;
}


static void
touch_export_sym(const char *name, uintptr_t ptr, uint32_t flags)
{
	 int name_len = strlen(name);
	 int h = sym_hash(name, name_len);
	 int cur = ex_sym_f[h];

	 while (cur != -1)
	 {
		  if (strcmp(ex_sym_name[cur], name) == 0)
		  {
			   break;
		  }
		  else
		  {
			   cur = ex_sym_n[cur];
		  }
	 }

	 if (cur == -1)
	 {
		  cur = ex_sym_count ++;
		  ex_sym_n[cur] = ex_sym_f[h];
		  ex_sym_f[h] = cur;

		  char *_name = chars + char_count;
		  memmove(_name, name, name_len);
		  char_count += name_len;
		  ex_sym_name[cur] = _name;
		  chars[char_count ++] = 0;
	 }
	 
	 ex_sym_ptr[cur] = ptr;
	 ex_sym_flags[cur] = flags;
}

static int
find_export_sym(const char *name, int touch)
{
	 int name_len = strlen(name);
	 int h = sym_hash(name, name_len);
	 int cur = ex_sym_f[h];

	 while (cur != -1)
	 {
		  if (strcmp(ex_sym_name[cur], name) == 0)
		  {
			   break;
		  }
		  else
		  {
			   cur = ex_sym_n[cur];
		  }
	 }

	 if (cur == -1 && touch)
	 {
		  cur = ex_sym_count ++;
		  ex_sym_n[cur] = ex_sym_f[h];
		  ex_sym_f[h] = cur;

		  char *_name = chars + char_count;
		  memmove(_name, name, name_len);
		  char_count += name_len;
		  chars[char_count ++] = 0;

		  ex_sym_name[cur] = _name;
	 }

	 return cur;
}

struct symtab_s *
fill_symbol_struct(void *elf, uint32_t symbol)
{
	 uint32_t i;
	 struct elf_s     *eh;
	 struct secthdr_s *sh;
	 struct symtab_s  *symtab;

	 eh = (struct elf_s*)elf;

	 for(i = 0; i < eh->e_shnum; i++)
	 {  
		  sh = (struct secthdr_s*)(elf + eh->e_shoff + (i * eh->e_shentsize));
		  if (sh->sh_type == SH_TYPE_SYMTAB)
		  {
			   symtab = (struct symtab_s*)((uintptr_t)elf + sh->sh_offset + (symbol * sh->sh_entsize));
			   return (struct symtab_s *)symtab;
		  }
	 }

	 return (struct symtab_s *)0;
}

uintptr_t
get_section_offset(void *elf, uint32_t info)
{
	 struct elf_s     *eh;
	 struct secthdr_s *sh;

	 eh = (struct elf_s*)elf;
	 sh = (struct secthdr_s*)(elf + eh->e_shoff + (info * eh->e_shentsize));

	 uintptr_t result;
	 if (sh->sh_type == SH_TYPE_NOBITS)
		  result = sh->sh_addr;
	 else result = sh->sh_offset;
	 return result;
}

static int
elf_header_check(void *elf)
{
	 struct elf_s *eh = (struct elf_s *)elf;
	 if (eh->e_magic != ELF_MAGIC)
	 {
		  // invalid signature
		  error("invalid signature\n");
		  return -1;
	 }

	 if(eh->e_type != 0x01)
	 {
		  error("error type = %d\n", eh->e_type);
		  // error type
		  return -1;
	 }

	 if(eh->e_machine != 0x03)
	 {
		  error("error machine type %d\n", eh->e_machine);
		  // error machine type
		  return -1;
	 }

	 if(eh->e_entry != 0x0)
	 {
		  error("error entry point %08x\n", eh->e_entry);
		  return -1;
	 }

	 return 0;
}

static int
elf_mod_parse(void *elf, const char *name, int export_symbol,
			  uintptr_t *common_data, uint32_t *common_size,
			  uintptr_t *mod_load_ptr, uintptr_t *mod_unload_ptr)
{
	 uint32_t i, x;
	 uintptr_t reloc_addr, mem_addr;
	 uintptr_t mod_load = 0, mod_unload = 0;
		
	 struct elf_s     *eh;
	 struct secthdr_s *sh;
	 struct reloc_s   *reloc;
	 struct symtab_s  *symtab;

	 uintptr_t cur_common_alloc = 0;
	 uintptr_t cur_common_align = 1;

	 eh = (struct elf_s*)elf;

	 /* do section header stuff here */
	 for (i = 0; i < eh->e_shnum; i++)
	 {  
		  sh = (struct secthdr_s*)(elf + eh->e_shoff + (i * eh->e_shentsize));		  
		  if (sh->sh_type == SH_TYPE_SYMTAB)
		  {
			   for(x = 0; x < sh->sh_size; x += sh->sh_entsize)
			   {
					symtab = (struct symtab_s*)(elf + sh->sh_offset + x);

					debug("found sym: [%s] info[%02x] size[%d] addr[%08x]",
						  get_symbol_string(elf, symtab->sym_name),
						  symtab->sym_info,
						  symtab->sym_size,
						  symtab->sym_address);
					if (symtab->sym_shndx != SHN_UNDEF && symtab->sym_shndx < 0xff00)
					{
						 debug(" value offset [%08x]", 
							   get_section_offset(elf, symtab->sym_shndx) +
							   symtab->sym_address);

						 switch (GET_SYMTAB_BIND(symtab->sym_info))
						 {
						 case 1:
							  debug(" global\n");
						 case 0:
							  if (strcmp(get_symbol_string(elf, symtab->sym_name),
										 MOD_LOAD_SYMBOL) == 0)
							  {
								   mod_load = get_section_offset(elf, symtab->sym_shndx) +
										symtab->sym_address + (uint32_t)elf;
							  }
							  else if (strcmp(get_symbol_string(elf, symtab->sym_name),
											  MOD_UNLOAD_SYMBOL) == 0)
							  {
								   mod_unload = get_section_offset(elf, symtab->sym_shndx) +
										symtab->sym_address + (uint32_t)elf;
							  }
							  // global
							  if (GET_SYMTAB_BIND(symtab->sym_info) == 1 && export_symbol)
								   elf_mod_touch_symbol(get_symbol_string(elf, symtab->sym_name),
														(void *)(symtab->sym_address + (uint32_t)elf), 0);
							  break;
						 case 2:
							  debug(" weak\n");
							  // weak
							  if (export_symbol)
								   elf_mod_create_symbol(get_symbol_string(elf, symtab->sym_name),
														 (void *)(symtab->sym_address + (uint32_t)elf), 0);
							  break;
						 }

					}
					else if (symtab->sym_shndx == SHN_COMMON)
					{
						 debug(" alloc %d byte align 0x%x\n",
							   symtab->sym_size, symtab->sym_address);
						 if (bsf(symtab->sym_address) != bsr(symtab->sym_address))
						 {
							  error(" bad align\n");
							  return -1;
						 }
						 if (symtab->sym_address > cur_common_align)
							  cur_common_align = symtab->sym_address;
						 cur_common_alloc = ((cur_common_alloc - 1) | (symtab->sym_address - 1)) + 1;
						 symtab->sym_address = cur_common_alloc;
						 cur_common_alloc += symtab->sym_size;
					}
					else
					{
						 debug(" shndx[%04x]\n", symtab->sym_shndx);
					}
			   }
		  }
		  else if (sh->sh_type == SH_TYPE_NOBITS)
		  {
			   debug(" alloc %d byte align 0x%x\n",
					 sh->sh_size, sh->sh_addralign);
			   if (bsf(sh->sh_addralign) != bsr(sh->sh_addralign))
			   {
					error(" bad align\n");
					return -1;
			   }
			   if (sh->sh_addralign > cur_common_align)
					cur_common_align = sh->sh_addralign;
			   cur_common_alloc = ((cur_common_alloc - 1) | (sh->sh_addralign - 1)) + 1;
			   sh->sh_addr = cur_common_alloc;
			   cur_common_alloc += sh->sh_size;
		  }
	 }

	 debug("common: alloc %d bytes, align 0x%x\n",
		   cur_common_alloc,
		   cur_common_align);

	 uintptr_t common_space;
	 if (cur_common_align > PAGE_SIZE)
	 {
		  error("align failed\n");
		  return -1;
	 }
	 else if (cur_common_alloc > 0)
	 {
		  common_space = (uintptr_t)balloc(cur_common_alloc);
		  debug("common %08x, size %d\n", common_space, cur_common_alloc);
		  memset((void *)common_space, 0, cur_common_alloc);
		  
		  *common_data = common_space;
		  *common_size = cur_common_alloc;
	 }
	 else
	 {
		  debug("no common\n");
		  *common_data = 0;
		  *common_size = 0;
	 }

	 if (mod_load == 0 || mod_unload == 0)
	 {
		  error("entry point was not found\n");
		  return -1;
	 }
	 else
	 {
		  *mod_load_ptr = mod_load;
		  *mod_unload_ptr = mod_unload;
	 }
        		
	 /* fill the relocation entries */
	 for (i = 0; i < eh->e_shnum; i++)
	 {  
		  sh = (struct secthdr_s*)(elf + eh->e_shoff + (i * eh->e_shentsize));

		  if (sh->sh_type == SH_TYPE_RELOC)
		  {
			   for (x = 0; x < sh->sh_size; x += sh->sh_entsize)
			   {
					reloc = (struct reloc_s*)(elf + sh->sh_offset + x);
					symtab = fill_symbol_struct(elf, GET_RELOC_SYM(reloc->rl_info));

					debug("reloc[%02x] offset[%08x] for [%s], sym offset[%08x]\n",
						  GET_RELOC_TYPE(reloc->rl_info),
						  reloc->rl_offset,
						  get_symbol_string(elf, symtab->sym_name),
						  symtab->sym_address
						 );

					mem_addr = (uintptr_t)elf + reloc->rl_offset;
					mem_addr += get_section_offset(elf, sh->sh_info);
                
					/* external reference (kernel symbol most likely) */
					if(symtab->sym_shndx == SHN_UNDEF)
					{
						 const char *sym_name = get_symbol_string(elf, symtab->sym_name);
						 int idx = find_export_sym(sym_name, 0);
						 if (idx == -1)
						 {
							  if (strcmp(sym_name, name) == 0)
							  {
								   reloc_addr = (uintptr_t)elf;
							  }
							  else if (strcmp(sym_name, "__MODULE_BEGIN__") == 0)
							  {
								   reloc_addr = (uint32_t)elf;
							  }
							  else 
							  {
								   // For skip the DDELINUX Symbol error
								   if (sym_name[0] != '_')
										error("unresolved dependency \"%s\", set with 0\n",
											  sym_name);
								   // goto failed_with_common_space;
								   reloc_addr = 0;
							  }
						 }
						 else
						 {
							  debug("extern symbol %s addr = %08x\n", sym_name, ex_sym_ptr[idx]);
							  reloc_addr = ex_sym_ptr[idx];
						 }
					}
					else if (symtab->sym_shndx < 0xff00)
					{
						 debug("section offset %08x, addr %08x\n", get_section_offset(elf, symtab->sym_shndx), symtab->sym_address);
						 if (((struct secthdr_s*)((uint32_t)eh + eh->e_shoff + (symtab->sym_shndx * eh->e_shentsize)))->sh_type == SH_TYPE_NOBITS)
							  reloc_addr = common_space;
						 else reloc_addr = (uint32_t)elf;
						 reloc_addr += get_section_offset(elf, symtab->sym_shndx);
						 reloc_addr += symtab->sym_address;
					}
					else if (symtab->sym_shndx == SHN_COMMON)
					{
						 reloc_addr = common_space + symtab->sym_address;
					}
					else
					{
						 error("UNHANDLED SYN_SHNDX\n");
					}

					debug("old value = %08x\n", *(uintptr_t *)mem_addr);

					switch (GET_RELOC_TYPE(reloc->rl_info))
					{
					case 0x01:
						 *(uintptr_t *)mem_addr = reloc_addr + *(uintptr_t *)mem_addr;
						 debug("fill abs address %08x to %08x\n", *(uintptr_t *)mem_addr, mem_addr);
						 break;
					case 0x02:
						 reloc_addr = reloc_addr - mem_addr;
						 *(uintptr_t *)mem_addr = reloc_addr + *(uintptr_t *)mem_addr ;
						 debug("fill rel address %08x to %08x\n", *(uintptr_t *)mem_addr, mem_addr);
						 break;
					default:
						 error("unsupported relocation type (%x)\n", GET_RELOC_TYPE(reloc->rl_info));
						 break;
					}
			   }
		  }
	 }
	 return 0;
	 
failed_with_common_space:
	 if (cur_common_alloc > 0)
		  bfree((void *)common_space);
	 return -1;
}

#define MAX_MOD_COUNT 1024

static int mi_count;
static struct mi_entry_s
{
	 const char *image;
	 uint32_t    image_size;
} mi_entry[MAX_MOD_COUNT];
	 
static void
elf_mod_register_all(void)
{
	 mi_count = 0;
	 
	 extern const char _binary_modules_start[];
	 extern const char _binary_modules_end[];
	 
	 const char *mod_image = _binary_modules_start;
	 const char *cur = mod_image;

	 while (1)
	 {
		  if (*cur != 0) break;
		  ++ cur;
		  
		  uint32_t name_len = *(const uint32_t *)cur;
		  cur += sizeof(uint32_t);

		  const char *name = cur;
		  cur += name_len + 1;

		  uint32_t image_size = *(const uint32_t *)cur;
		  cur += sizeof(uint32_t);

		  mi_entry[mi_count].image = cur;
		  mi_entry[mi_count].image_size = image_size;
		  
		  touch_export_sym(name, (uintptr_t)&mi_entry[mi_count], 0);
		  kprintf("module %s found\n", name, image_size);

		  ++ mi_count;

		  cur += image_size;
	 }
}

#define EXPORT(name) touch_export_sym("ekf_"#name, (uintptr_t)&name, 0)

extern volatile ptab_entry_t vpt[];
extern volatile pdir_entry_t vpd[];

int
elf_mod_init(void)
{
	 ex_sym_count = 0;
	 char_count = 0;
	 
	 memset(ex_sym_f, -1, sizeof(ex_sym_f));

	 EXPORT(cprintf);
	 EXPORT(vcprintf);
	 EXPORT(cputchar);

	 EXPORT(kprintf);
	 EXPORT(vkprintf);
	 EXPORT(vga_put_sc);
	 
	 EXPORT(readline);

	 EXPORT(snprintf);
	 EXPORT(vsnprintf);
	 
	 EXPORT(strcmp);
	 EXPORT(strncmp);
	 EXPORT(strcat);
	 EXPORT(strcpy);
	 EXPORT(strfind);
	 EXPORT(strncpy);

	 EXPORT(memset);
	 EXPORT(memmove);
	 touch_export_sym("memcpy", (uintptr_t)&memmove, 0);
	 
	 EXPORT(hpet);

	 EXPORT(lcpu_id);
	 
	 EXPORT(context_fill);
	 EXPORT(context_switch);

	 EXPORT(intr_irq_handler_set);
	 EXPORT(intr_ex_handler_set);
	 EXPORT(intr_sys_handler_set);
	 EXPORT(intr_trap_from_user);
	 EXPORT(intr_timer_set);
	 EXPORT(intr_irq_eoi);
	 
	 EXPORT(intr_irq_line_enable);
	 EXPORT(intr_irq_line_disable);

	 touch_export_sym("vpt", (uintptr_t)&vpt[0], 0);
	 touch_export_sym("vpd", (uintptr_t)&vpd[0], 0);
	 EXPORT(vpt_set);
	 EXPORT(vpt_get);
	 EXPORT(mmap_set);
	 EXPORT(mmap_get);
	 EXPORT(__fix_addr);
	 
	 EXPORT(pmem_page_alloc);
	 EXPORT(pmem_page_free);

	 EXPORT(valloc);
	 EXPORT(vfree);
	 EXPORT(mmio_alloc);
	 EXPORT(mmio_free);
	 EXPORT(balloc);
	 EXPORT(bfree);
	 EXPORT(palloc);
	 EXPORT(pfree);
	 EXPORT(alloc);
	 EXPORT(free);

	 EXPORT(jump_user);
	 EXPORT(jump_user_from_trapframe);
	 EXPORT(jump_user_from_trap_sys);

	 touch_export_sym("sysconf", (uintptr_t)&sysconf, 0);
	 
	 elf_mod_register_all();
	 
	 return 0;
}

int
elf_mod_set_symbol(const char *name, void *ptr, uint32_t flags)
{
	 int idx = find_export_sym(name, 0);
	 if (idx == -1) return -1;

	 ex_sym_ptr[idx] = (uintptr_t)ptr;
	 ex_sym_flags[idx] = flags;
	 
	 return 0;
}

void
elf_mod_touch_symbol(const char *name, void *ptr, uint32_t flags)
{
	 int idx = find_export_sym(name, 1);

	 ex_sym_ptr[idx] = (uintptr_t)ptr;
	 ex_sym_flags[idx] = flags;	 
}

int
elf_mod_create_symbol(const char *name, void *ptr, uint32_t flags)
{
	 int idx = find_export_sym(name, 1);
	 if (idx != ex_sym_count - 1) return -1;

	 ex_sym_ptr[idx] = (uintptr_t)ptr;
	 ex_sym_flags[idx] = flags;
	 
	 return 0;
}

int
elf_mod_get_symbol(const char *name, void **ptr, uint32_t *flags)
{
	 int idx = find_export_sym(name, 0);
	 if (idx == -1) return -1;
	 
	 if (ptr != NULL)   *ptr = (void *)ex_sym_ptr[idx];
	 if (flags != NULL) *flags = ex_sym_flags[idx];

	 return 0;
}

int
elf_mod_load(const char *name, const char *image, uint32_t image_size, struct elf_mod_info_s *info, void *arg)
{
	 info->image = (uintptr_t)image;
	 info->image_size = image_size;

	 if (elf_header_check((void *)image))
		  return -1;

	 struct elf_s *eh = (struct elf_s *)image;
	 if ((info->ptr = (uintptr_t)balloc(image_size)) == 0)
		  return -1;
	 
	 memmove((void *)info->ptr, image, image_size);

	 debug("parsing\n");
	 if (elf_mod_parse((void *)info->ptr, name, 0,
					   &info->common_ptr, &info->common_size,
					   &info->load_ptr, &info->unload_ptr) != 0)
	 {
		  bfree((void *)info->ptr);
		  return -1;
	 }

	 kprintf("mod %s: %08x size %08x common %08x\n",
			 name, info->ptr,
			 image_size, info->common_size);

	 debug("load() begin\n");
	 if (((int(*)(void *))(info->load_ptr))(arg) != 0)
	 {
		  if (info->common_size != 0)
			   bfree((void *)info->common_ptr);
		  bfree((void *)info->ptr);
		  error("load() failed\n");
		  return -1;
	 }
	 debug("load() success\n");

	 return 0;
}

int
elf_mod_load_by_name(const char *name, struct elf_mod_info_s *info, void *arg)
{
	 struct mi_entry_s *entry;
	 if (elf_mod_get_symbol(name, (void **)&entry, NULL) != 0) return -1;
	 if (elf_mod_load(name, entry->image, entry->image_size, info, arg) != 0) return -1;
	 return 0;
}
